---
title: Getting Started
---

## Requirements

You need a LangGraph Cloud API server. You can start a server locally via [LangGraph Studio](https://github.com/langchain-ai/langgraph-studio) or use [LangSmith](https://www.langchain.com/langsmith) for a hosted version.

The state of the graph you are using must have a `messages` key with a list of LangChain-alike messages.

## New project from template

import { Steps, Step } from "fumadocs-ui/components/steps";

<Steps>
  <Step>
  ### Create a new project based on the LangGraph assistant-ui template

```sh
npx create-assistant-ui@latest -t langgraph my-app
```

  </Step>
  <Step>
  ### Set environment variables

Create a `.env.local` file in your project with the following variables:

```sh
# LANGCHAIN_API_KEY=your_api_key # for production
# LANGGRAPH_API_URL=your_api_url # for production
NEXT_PUBLIC_LANGGRAPH_API_URL=your_api_url # for development (no api key required)
NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_id
```

  </Step>
</Steps>

import { InstallCommand } from "@/components/docs/install-command";

## Installation in existing React project

<Steps>
  <Step>

### Install dependencies

<InstallCommand npm={["@assistant-ui/react", "@assistant-ui/react-langgraph", "@langchain/langgraph-sdk"]} />

  </Step>
    <Step>

### Setup a proxy backend endpoint (optional, for production)

<Callout type="warn">
  This example forwards every request to the LangGraph server directly from the
  browser. For production use-cases, you should limit the API calls to the
  subset of endpoints that you need and perform authorization checks.
</Callout>

```tsx twoslash title="@/api/api/[...path]/route.ts"
import { NextRequest, NextResponse } from "next/server";

function getCorsHeaders() {
  return {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
    "Access-Control-Allow-Headers": "*",
  };
}

async function handleRequest(req: NextRequest, method: string) {
  try {
    const path = req.nextUrl.pathname.replace(/^\/?api\//, "");
    const url = new URL(req.url);
    const searchParams = new URLSearchParams(url.search);
    searchParams.delete("_path");
    searchParams.delete("nxtP_path");
    const queryString = searchParams.toString()
      ? `?${searchParams.toString()}`
      : "";

    const options: RequestInit = {
      method,
      headers: {
        "x-api-key": process.env["LANGCHAIN_API_KEY"] || "",
      },
    };

    if (["POST", "PUT", "PATCH"].includes(method)) {
      options.body = await req.text();
    }

    const res = await fetch(
      `${process.env["LANGGRAPH_API_URL"]}/${path}${queryString}`,
      options,
    );

    return new NextResponse(res.body, {
      status: res.status,
      statusText: res.statusText,
      headers: {
        ...res.headers,
        ...getCorsHeaders(),
      },
    });
  } catch (e: any) {
    return NextResponse.json({ error: e.message }, { status: e.status ?? 500 });
  }
}

export const GET = (req: NextRequest) => handleRequest(req, "GET");
export const POST = (req: NextRequest) => handleRequest(req, "POST");
export const PUT = (req: NextRequest) => handleRequest(req, "PUT");
export const PATCH = (req: NextRequest) => handleRequest(req, "PATCH");
export const DELETE = (req: NextRequest) => handleRequest(req, "DELETE");

// Add a new OPTIONS handler
export const OPTIONS = () => {
  return new NextResponse(null, {
    status: 204,
    headers: {
      ...getCorsHeaders(),
    },
  });
};
```

  </Step>
  <Step>

### Setup helper functions

```tsx twoslash include chatApi title="@/lib/chatApi.ts"
// @filename: /lib/chatApi.ts

// ---cut---
import { Client } from "@langchain/langgraph-sdk";
import { LangChainMessage, LangGraphSendMessageConfig } from "@assistant-ui/react-langgraph";

const createClient = () => {
  const apiUrl = process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"] || "/api";
  return new Client({
    apiUrl,
  });
};

export const createThread = async () => {
  const client = createClient();
  return client.threads.create();
};

export const getThreadState = async (
  threadId: string,
): Promise<ThreadState<{ messages: LangChainMessage[] }>> => {
  const client = createClient();
  return client.threads.getState(threadId);
};

export const sendMessage = async (params: {
  threadId: string;
  messages: LangChainMessage;
  config?: LangGraphSendMessageConfig;
}) => {
  const client = createClient();
  return client.runs.stream(
    params.threadId,
    process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!,
    {
      input: {
        messages: params.messages,
      },
      streamMode: "messages",
      ...params.config
    },
  );
};
```

  </Step>
  <Step>

### Define a `MyAssistant` component

```tsx twoslash include MyAssistant title="@/components/MyAssistant.tsx"
// @filename: /components/MyAssistant.tsx
// @include: chatApi

// ---cut---
"use client";

import { Thread } from "@/components/assistant-ui/thread";
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";

import { createThread, getThreadState, sendMessage } from "@/lib/chatApi";

export function MyAssistant() {
  const runtime = useLangGraphRuntime({
    stream: async (messages, { initialize, config }) => {
      const { externalId } = await initialize();
      if (!externalId) throw new Error("Thread not found");
      return sendMessage({
        threadId: externalId,
        messages,
        config
      });
    },
    create: async () => {
      const { thread_id } = await createThread();
      return { externalId: thread_id };
    },
    load: async (externalId) => {
      const state = await getThreadState(externalId);
      return {
        messages: state.values.messages,
        interrupts: state.tasks[0]?.interrupts,
      };
    },
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <Thread />
    </AssistantRuntimeProvider>
  );
}
```

  </Step>
  <Step>

### Use the `MyAssistant` component

```tsx twoslash title="@/app/page.tsx" {2,8}
// @include: MyAssistant
// @filename: /app/page.tsx
// ---cut---
import { MyAssistant } from "@/components/MyAssistant";

export default function Home() {
  return (
    <main className="h-dvh">
      <MyAssistant />
    </main>
  );
}
```

  </Step>
  <Step>

### Setup environment variables

Create a `.env.local` file in your project with the following variables:

```sh
# LANGCHAIN_API_KEY=your_api_key # for production
# LANGGRAPH_API_URL=your_api_url # for production
NEXT_PUBLIC_LANGGRAPH_API_URL=your_api_url # for development (no api key required)
NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=your_graph_id
```

  </Step>
  <Step>

### Setup UI components

Follow the [UI Components](/docs/ui/shadcn-ui/Thread) guide to setup the UI components.

  </Step>
</Steps>

## Advanced APIs

### Message Accumulator

The `LangGraphMessageAccumulator` lets you append messages incoming from the server to replicate the messages state client side.

```typescript
import {
  LangGraphMessageAccumulator,
  appendLangChainChunk,
} from "@assistant-ui/react-langgraph";

const accumulator = new LangGraphMessageAccumulator({
  appendMessage: appendLangChainChunk,
});

// Add new chunks from the server
if (event.event === "messages/partial") accumulator.addMessages(event.data);
```

### Message Conversion

Use `convertLangChainMessages` to transform LangChain messages to assistant-ui format:

```typescript
import { convertLangChainMessages } from "@assistant-ui/react-langgraph";

const threadMessage = convertLangChainMessages(langChainMessage);
```

## Thread Management

### Basic Thread Support

The `useLangGraphRuntime` hook now includes built-in thread management capabilities:

```typescript
const runtime = useLangGraphRuntime({
  stream: async (messages, { initialize, config }) => {
    // initialize() creates or loads a thread and returns its IDs
    const { remoteId, externalId } = await initialize();
    // Use externalId (your backend's thread ID) for API calls
    return sendMessage({ threadId: externalId, messages, config });
  },
  create: async () => {
    // Called when creating a new thread
    const { thread_id } = await createThread();
    return { externalId: thread_id };
  },
  load: async (externalId) => {
    // Called when loading an existing thread
    const state = await getThreadState(externalId);
    return {
      messages: state.values.messages,
      interrupts: state.tasks[0]?.interrupts,
    };
  },
});
```

### Cloud Persistence

For persistent thread history across sessions, integrate with assistant-cloud:

```typescript
const runtime = useLangGraphRuntime({
  cloud: new AssistantCloud({
    baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL,
  }),
  // ... stream, create, load functions
});
```

See the [Cloud Persistence guide](/docs/cloud/persistence/langgraph) for detailed setup instructions.

## Interrupt Persistence

LangGraph supports interrupting the execution flow to request user input or handle specific interactions. These interrupts can be persisted and restored when switching between threads:

1. Make sure your thread state type includes the `interrupts` field
2. Return the interrupts from the `load` function along with the messages
3. The runtime will automatically restore the interrupt state when switching threads

This feature is particularly useful for applications that require user approval flows, multi-step forms, or any other interactive elements that might span multiple thread switches.
